#include "RandomObjectGenerator.h"
#include "Ingredient.h"
#include "Misc.h"
#include "Potion.h"
#include "Tool.h"
#include "Util.h"
#include "Game.h"
#include "Npc.h"
#include "Trap.h"
#include "Resource.h"
#include "ItemContainer.h"
#include "Food.h"
#include "Shield.h"
#include "Armor.h"
#include "ObjectDatabase.h"
#include "Scroll.h"
#include "Ammo.h"
#include "MeleeWeapon.h"
#include "RangedWeapon.h"

Item* RandomObjectGenerator::getRandomItem(int dlevel, int type) {
	std::vector<RandomObjectWeight*> potentialItems;
	int totalWeight = 0;
	if(type & RANDOM_ITEM_FOOD) {
		for(int i = 0; i < NUM_FOOD_TYPES; i++) {
			const FoodInfo& info = g_objectDatabase->getFoodInfo(FoodType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_FOOD, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_INGREDIENT) {
		for(int i = 0; i < NUM_INGREDIENT_TYPES; i++) {
			const IngredientInfo& info = g_objectDatabase->getIngredientInfo(IngredientType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_INGREDIENT, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_MISC) {
		for(int i = 0; i < NUM_MISC_TYPES - 1; i++) {
			const MiscInfo& info = g_objectDatabase->getMiscInfo(MiscType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_MISC, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_POTION) {
		for(int i = 0; i < NUM_POTION_TYPES; i++) {
			const PotionInfo& info = g_objectDatabase->getPotionInfo(PotionType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_POTION, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_TOOL) {
		for(int i = 0; i < NUM_TOOL_TYPES; i++) {
			const ToolInfo& info = g_objectDatabase->getToolInfo(ToolType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_TOOL, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_MELEE_WEAPON) {
		for(int i = 0; i < NUM_MELEE_WEAPON_TYPES; i++) {
			const MeleeWeaponInfo& info = g_objectDatabase->getMeleeWeaponInfo(MeleeWeaponType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_MELEE_WEAPON, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_SCROLL) {
		for(int i = 0; i < NUM_SCROLL_TYPES; i++) {
			const ScrollInfo& info = g_objectDatabase->getScrollInfo(ScrollType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_SCROLL, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_SHIELD) {
		for(int i = 0; i < NUM_SHIELD_TYPES; i++) {
			const ShieldInfo& info = g_objectDatabase->getShieldInfo(ShieldType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_SHIELD, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_ARMOR) {
		for(int i = 0; i < NUM_ARMOR_TYPES; i++) {
			const ArmorInfo& info = g_objectDatabase->getArmorInfo(ArmorType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_ARMOR, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_AMMO) {
		for(int i = 0; i < NUM_AMMO_TYPES; i++) {
			const AmmoInfo& info = g_objectDatabase->getAmmoInfo(AmmoType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_AMMO, i, totalWeight));
		}
	}
	if(type & RANDOM_ITEM_RANGED_WEAPON) {
		for(int i = 0; i < NUM_RANGED_WEAPON_TYPES; i++) {
			const RangedWeaponInfo& info = g_objectDatabase->getRangedWeaponInfo(RangedWeaponType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_IT_RANGED_WEAPON, i, totalWeight));
		}
	}

	int index = binarySearch(potentialItems, rand(totalWeight));
	Item* item = createItem(potentialItems[index]->objectType, potentialItems[index]->type, dlevel);
	item->setStatus(Status(rand(3)), true);

	for(unsigned int i = 0; i < potentialItems.size(); i++) {
		delete potentialItems[i];
	}
	potentialItems.erase(potentialItems.begin(), potentialItems.end());
	if(DEBUG)
		g_textLog->addLine("Generated Item: " + item->debugString());
	return item;
}

Npc* RandomObjectGenerator::getRandomNpc(int dlevel) {
	std::vector<RandomObjectWeight*> potentialItems;
	int totalWeight = 0;
	for(int i = 0; i < NUM_NPC_TYPES; i++) {
		const NpcInfo& info = g_objectDatabase->getNpcInfo(NpcType(i));
		totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
		potentialItems.push_back(createRandomObjectWeight(GT_CH_NPC, i, totalWeight));
	}
	int index = binarySearch(potentialItems, rand(totalWeight));
	Npc* npc = new Npc(NpcType(potentialItems[index]->type), true);
	if(npc->isHumanoid()) {
		bool twoHanded = false;
		if(x_in_y(2,5)) {
			Item* item = getRandomItem(dlevel, RANDOM_ITEM_MELEE_WEAPON /*| RANDOM_ITEM_RANGED_WEAPON*/);
			npc->forceItemInInventory(item);
			npc->equip(EL_LEFT_HAND, item, false);
			twoHanded = ((Weapon*)item)->getGripType() == GT_TWO;
		}
		if(x_in_y(2,5) && !twoHanded) {
			int type = RANDOM_ITEM_MELEE_WEAPON | RANDOM_ITEM_SHIELD /*| RANDOM_ITEM_RANGED_WEAPON*/;
			Item* item = getRandomItem(dlevel, type);
			npc->forceItemInInventory(item);
			if(Weapon::isWeapon(item) || ((Weapon*)item)->getGripType() != GT_TWO)
				npc->equip(EL_RIGHT_HAND, item, false);
		}
		/*if(x_in_y(1,3)) {
			// need to improve random object generator to allow for more specific types
			npc->equip(EL_HEAD, getRandomItem(dlevel, RANDOM_ITEM_ARMOR), false);
		}*/
	}

	for(unsigned int i = 0; i < potentialItems.size(); i++) {
		delete potentialItems[i];
	}
	potentialItems.erase(potentialItems.begin(), potentialItems.end());
	if(DEBUG)
		g_textLog->addLine("Generated Npc: " + npc->debugString());
	return npc;
}

Obstacle* RandomObjectGenerator::getRandomObstacle(int dlevel, int type) {
	std::vector<RandomObjectWeight*> potentialItems;
	int totalWeight = 0;
	if(type & RANDOM_OBS_ITEMCONTAINER) {
		for(int i = 0; i < NUM_ITEMCONTAINER_TYPES; i++) {
			const ItemContainerInfo& info = g_objectDatabase->getItemContainerInfo(ItemContainerType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_OB_ITEMCONTAINER, i, totalWeight));
		}
	}
	if(type & RANDOM_OBS_RESOURCE) {
		for(int i = 0; i < NUM_RESOURCE_TYPES; i++) {
			const ResourceInfo& info = g_objectDatabase->getResourceInfo(ResourceType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_OB_RESOURCE, i, totalWeight));
		}
	}
	if(type & RANDOM_OBS_TRAP) {
		for(int i = 0; i < NUM_TRAP_TYPES; i++) {
			const TrapInfo& info = g_objectDatabase->getTrapInfo(TrapType(i));
			totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
			potentialItems.push_back(createRandomObjectWeight(GT_OB_TRAP, i, totalWeight));
		}
	}
	int index = binarySearch(potentialItems, rand(totalWeight));
	Obstacle* obstacle = createObstacle(potentialItems[index]->objectType, potentialItems[index]->type, dlevel);

	for(unsigned int i = 0; i < potentialItems.size(); i++) {
		delete potentialItems[i];
	}
	potentialItems.erase(potentialItems.begin(), potentialItems.end());
	if(DEBUG)
		g_textLog->addLine("Generated Obstacle: " + obstacle->debugString());
	return obstacle;
}

Obstacle* RandomObjectGenerator::createObstacle(GenericType objectType, int type, int dlevel) {
	switch(objectType) {
	case GT_OB_ITEMCONTAINER:
		return createItemContainer(ItemContainerType(type), dlevel);
	case GT_OB_RESOURCE:
		return new Resource(ResourceType(type), rand_plus_one(5));
	case GT_OB_TRAP:
		return new Trap(TrapType(type), rand(5));
	}
	return NULL;
}

ItemContainer* RandomObjectGenerator::createItemContainer(ItemContainerType type, int dlevel) {
	int numItems;
	ItemContainer* ic;
	switch(type) {
	case ICT_BOX:
		ic = new ItemContainer(type, (one_in(4) ? LockType(rand_plus_one(NUM_LOCK_TYPES-1)) : LT_NONE), x_in_y(4,5));
		numItems = rand(3);
		break;
	case ICT_CHEST:
		ic = new ItemContainer(type, (one_in(2) ? LockType(rand_plus_one(NUM_LOCK_TYPES-1)) : LT_NONE), x_in_y(4,5));
		numItems = rand_plus_one(5);
		break;
	case ICT_POTTERY:
		ic = new ItemContainer(type);
		numItems = rand(2);
		break;
	}
	for(int i = 0; i < numItems; i++) {
		ic->addItem(getRandomItem(dlevel));
	}
	return ic;
}

Item* RandomObjectGenerator::createItem(GenericType objectType, int type, int dlevel) {
	switch(objectType) {
	case GT_IT_FOOD:
		return new Food(FoodType(type));
	case GT_IT_INGREDIENT:
		return new Ingredient(IngredientType(type));
	case GT_IT_MISC:
		return new Misc(MiscType(type));
	case GT_IT_POTION:
		return new Potion(PotionType(type), std::max(dlevel/5, 1));
	case GT_IT_TOOL:
		return createTool(ToolType(type), dlevel);
	case GT_IT_MELEE_WEAPON:
		return createMeleeWeapon(MeleeWeaponType(type), dlevel);
	case GT_IT_RANGED_WEAPON:
		return createRangedWeapon(RangedWeaponType(type), dlevel);
	case GT_IT_SCROLL:
		return new Scroll(ScrollType(type));
	case GT_IT_SHIELD:
		return createShield(ShieldType(type), dlevel);
	case GT_IT_ARMOR:
		return createArmor(ArmorType(type), dlevel);
	case GT_IT_AMMO:
		return new Ammo(AmmoType(type), rand_plus_one(12));
	}
	return NULL;
}

Tool* RandomObjectGenerator::createTool(ToolType type, int dlevel) {
	ToolPrefix prefix = ToolPrefix(rand(NUM_TOOL_PREFIXES));
	ToolSuffix suffix = TS_OF_NONE;
	if(one_in(20))
		suffix = ToolSuffix(rand(NUM_TOOL_SUFFIXES));
	return new Tool(type, prefix, suffix, one_in(30));
}

MeleeWeapon* RandomObjectGenerator::createMeleeWeapon(MeleeWeaponType type, int dlevel) {
	MeleeWeaponPrefix prefix = MWP_NONE;
	MeleeWeaponSuffix suffix = MWS_OF_NONE;
	if(one_in(10))
		prefix = MeleeWeaponPrefix(rand(NUM_MELEE_WEAPON_PREFIXES));
	if(one_in(20))
		suffix = MeleeWeaponSuffix(rand(NUM_MELEE_WEAPON_SUFFIXES));

	return new MeleeWeapon(type, getMaterialType(dlevel), prefix, suffix, one_in(30));
}

RangedWeapon* RandomObjectGenerator::createRangedWeapon(RangedWeaponType type, int dlevel) {
	RangedWeaponPrefix prefix = RWP_NONE;
	RangedWeaponSuffix suffix = RWS_OF_NONE;
	if(one_in(10))
		prefix = RangedWeaponPrefix(rand(NUM_RANGED_WEAPON_PREFIXES));
	if(one_in(20))
		suffix = RangedWeaponSuffix(rand(NUM_RANGED_WEAPON_SUFFIXES));

	return new RangedWeapon(type, prefix, suffix, one_in(30), float(rand(5)) / 4.0f);
}

Shield* RandomObjectGenerator::createShield(ShieldType type, int dlevel) {
	ShieldPrefix prefix = SP_NONE;
	ShieldSuffix suffix = SS_OF_NONE;
	if(one_in(10))
		prefix = ShieldPrefix(rand(NUM_SHIELD_PREFIXES));
	if(one_in(20))
		suffix = ShieldSuffix(rand(NUM_SHIELD_SUFFIXES));

	return new Shield(type, getMaterialType(dlevel), prefix, suffix, one_in(30));
}

Armor* RandomObjectGenerator::createArmor(ArmorType type, int dlevel) {
	ArmorPrefix prefix = AP_NONE;
	ArmorSuffix suffix = AS_OF_NONE;
	if(one_in(10))
		prefix = ArmorPrefix(rand(NUM_ARMOR_PREFIXES));
	if(one_in(20))
		suffix = ArmorSuffix(rand(NUM_ARMOR_SUFFIXES));

	return new Armor(type, getMaterialType(dlevel, false), prefix, suffix, one_in(30));
}

MaterialType RandomObjectGenerator::getMaterialType(int dlevel, bool allowWooden) {
	std::vector<RandomObjectWeight*> potentialItems;
	int totalWeight = 0;
	for(int i = allowWooden ? 0 : MT_COPPER; i < NUM_MATERIAL_TYPES; i++) {
		const MaterialInfo& info = g_objectDatabase->getMaterialInfo(MaterialType(i));
		totalWeight += modifiedWeight(dlevel, info.dlevel, info.commonality);
		potentialItems.push_back(createRandomObjectWeight(GT_IT_INGREDIENT, i, totalWeight));
	}
	int index = binarySearch(potentialItems, rand(totalWeight));
	MaterialType type = MaterialType(potentialItems[index]->type);

	for(unsigned int i = 0; i < potentialItems.size(); i++) {
		delete potentialItems[i];
	}
	potentialItems.erase(potentialItems.begin(), potentialItems.end());
	return type;
}

int RandomObjectGenerator::binarySearch(std::vector<RandomObjectWeight*> vec, int val) {
	unsigned int first = 0, last = vec.size()-1;
	while (first < last) {
		int mid = (first + last) / 2;
		if (val > vec[mid]->weight) 
			first = mid + 1;
		else if (val < vec[mid]->weight) {
			if(mid == 0 || val > vec[mid-1]->weight)
				return mid;
			last = mid - 1;
		} else
			return mid+1;
	}
	return first;
}

RandomObjectWeight* RandomObjectGenerator::createRandomObjectWeight(GenericType objectType, int type, int weight) {
	RandomObjectWeight* rit = new RandomObjectWeight;
	rit->objectType = objectType;
	rit->type = type;
	rit->weight = weight;
	return rit;
}

int RandomObjectGenerator::modifiedWeight(int dlevel, int ilevel, int commonality) {
	float diff = float(abs(dlevel - ilevel));
	float mod = std::max((50.0f - diff) / 50.0f, 0.1f);
	return int(commonality * mod);
}